package com.dragon.backstage.aspect;

import com.dragon.backstage.service.LoginService;
import com.dragon.common.annotation.UserOperatorLog;
import com.dragon.common.constant.BasicConstant;
import com.dragon.common.enums.BusinessStatus;
import com.dragon.common.utils.AjaxResult;
import com.dragon.common.utils.MdcUtils;
import com.dragon.common.utils.ServletUtils;
import com.dragon.common.utils.ip.IpUtils;
import com.dragon.common.utils.time.DateTimePattern;
import com.dragon.framework.json.serializer.MultipartFileSerializer;
import com.dragon.framework.json.serializer.ServletRequestSerializer;
import com.dragon.framework.json.serializer.ServletResponseSerializer;
import com.dragon.framework.manager.AsyncManager;
import com.dragon.framework.manager.factory.AsyncFactory;
import com.dragon.interfaces.entity.UserOperateLog;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 用户操作日志切面
 *
 * @author dragon
 * @version 1.0
 * @date 2024/8/5 13:34
 */
@Slf4j
@Aspect
@Component
public class UserOperatorLogAspect {

    /**
     * jackson过滤器ID
     */
    public static final String JSON_FILTER_ID = "userOperatorLogJsonFilter";

    /**
     * 排除敏感属性字段
     */
    public static final Set<String> EXCLUDE_PROPERTIES = Set.of("password", "oldPassword", "newPassword", "confirmPassword");

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new NamedThreadLocal<>("OperateLog Cost Time");

    /**
     * 单例模式 for ObjectMapper
     */
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();


    static {
        // 创建一个SimpleModule并添加自定义序列化器
        SimpleModule module = new SimpleModule();
        // MultipartFile序列化器
        module.addSerializer(MultipartFile.class, new MultipartFileSerializer());
        // ServletRequest序列化器
        module.addSerializer(ServletRequest.class, new ServletRequestSerializer());
        // ServletResponse序列化器
        module.addSerializer(ServletResponse.class, new ServletResponseSerializer());
        // 注册模块到ObjectMapper
        OBJECT_MAPPER.registerModule(module);

        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateTimePattern.DATETIME_PATTERN)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.DATETIME_PATTERN)));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DateTimePattern.DATE_PATTERN)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.DATE_PATTERN)));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateTimePattern.TIME_PATTERN)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.TIME_PATTERN)));

        OBJECT_MAPPER.registerModule(javaTimeModule);
    }

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private LoginService loginService;


    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(operatorLog)")
    public void boBefore(JoinPoint joinPoint, UserOperatorLog operatorLog) {
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(operatorLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, UserOperatorLog operatorLog, Object jsonResult) {
        handleLog(joinPoint, operatorLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "@annotation(operatorLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, UserOperatorLog operatorLog, Exception e) {
        handleLog(joinPoint, operatorLog, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, UserOperatorLog operatorLog, final Exception e, Object jsonResult) {
        try {
            // 获取当前的用户
            String account = loginService.getAccount();

            // *======== 数据库日志 =========*//
            UserOperateLog userOperateLog = new UserOperateLog();
            userOperateLog.setStatus(BusinessStatus.SUCCESS.getCode().byteValue());
            // 通过返回结果判断操作状态
            if (jsonResult != null) {
                if (jsonResult instanceof AjaxResult<?> ajaxResult) {
                    if (!ajaxResult.getIsSuccessful()) {
                        userOperateLog.setStatus(BusinessStatus.FAIL.getCode().byteValue());
                        userOperateLog.setErrorMsg(StringUtils.substring(ajaxResult.getMessage(), 0, 2000));
                    }
                }
            }
            // 请求ID
            String traceId = MdcUtils.safeGenerate(BasicConstant.TRACE_ID);
            userOperateLog.setTraceId(traceId);
            // 请求的地址
            String ip = IpUtils.getIpAddr();
            userOperateLog.setRequestIp(ip);
            // 请求URL
            String queryString = ServletUtils.getRequest().getQueryString();
            userOperateLog.setRequestUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI() + (StringUtils.isBlank(queryString) ? "" : "?" + queryString), 0, 255));
            // 登陆用户
            userOperateLog.setOperateAccount(StringUtils.isBlank(account) ? null : account);

            if (e != null) {
                userOperateLog.setStatus(BusinessStatus.FAIL.getCode().byteValue());
                userOperateLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            userOperateLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            userOperateLog.setRequestMethod(ServletUtils.getRequest().getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, operatorLog, userOperateLog, jsonResult);
            // 设置消耗时间
            userOperateLog.setCostTime(System.currentTimeMillis() - TIME_THREAD_LOCAL.get());
            // 操作时间
            userOperateLog.setOperateTime(LocalDateTime.now());
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.insertUserOperateLog(userOperateLog));
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("UserOperatorLogAspect 异常信息:{}", exp.getMessage(), exp);
        } finally {
            TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log        操作日志注解
     * @param operateLog 操作日志实体
     * @throws Exception 异常
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, UserOperatorLog log, UserOperateLog operateLog, Object jsonResult) throws Exception {
        // 设置action动作
        operateLog.setBusinessType(log.businessType().getCode());
        // 设置标题
        operateLog.setTitle(log.title());
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData()) {
            // 排除字段Set
            Set<String> excludeParamNameSets = new HashSet<>(16);
            String[] excludeParamNames = log.excludeParamNames();
            if (excludeParamNames != null && excludeParamNames.length > 0) {
                excludeParamNameSets = new HashSet<>(Arrays.asList(excludeParamNames));
            }
            // 添加默认排除字段
            excludeParamNameSets.addAll(EXCLUDE_PROPERTIES);

            // 敏感字段过滤器
            SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(JSON_FILTER_ID, SimpleBeanPropertyFilter.serializeAllExcept(excludeParamNameSets));
            // 获取参数的信息，传入到数据库中。
            String args = OBJECT_MAPPER
                // 设置过滤器
                .setFilterProvider(filterProvider)
                // 定义一个“混入”接口或抽象类，其中包含Jackson注解，然后将这个“混入”指定给目标类使用。
                // 通过ObjectMapper的addMixIn方法，你可以将这些自定义的序列化或反序列化规则“混合”进目标类中
                // 例如，假设你有一个第三方库提供的类ThirdPartyClass，你无法修改它的源代码，但你想在序列化时忽略它的某个字段。你可以这样做
                .addMixIn(Object.class, FieldFilterMixIn.class)
                .writeValueAsString(getJoinPointArgs(joinPoint));
            operateLog.setRequestParam(args);
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData() && Objects.nonNull(jsonResult)) {
            operateLog.setJsonResult(objectMapper.writeValueAsString(jsonResult));
        }
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint joinPoint
     * @return 请求参数
     */
    private Map<String, Object> getJoinPointArgs(JoinPoint joinPoint) {
        Map<String, Object> paramMap = new HashMap<>(16);

        // 参数名称
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();

        for (int i = 0; i < paramNames.length; i++) {
            try {
                String paramName = paramNames[i];
                Object paramValue = paramValues[i];

                paramMap.put(paramName, paramValue);
            } catch (Exception e) {
                log.error("UserOperatorLogAspect 异常信息:{}", e.getMessage(), e);
            }
        }

        return paramMap;
    }

    /**
     * 定义一个类或接口用户给过滤器使用
     */
    @JsonFilter(JSON_FILTER_ID)
    interface FieldFilterMixIn {
    }

}
